import java.lang.reflect.InvocationTargetException;

import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;

import arkin.AND;
import arkin.Action;
import arkin.ActionDifferentialWheel;
import arkin.ActionLED;
import arkin.ActionPen;
import arkin.Actuator;
import arkin.Arbiter;
import arkin.Behavior;
import arkin.Boolean;
import arkin.Constante;
import arkin.DifferentialWheel;
import arkin.DistanceSensor;
import arkin.EQUAL;
import arkin.EntryState;
import arkin.FSM;
import arkin.GT;
import arkin.GTE;
import arkin.IVisitor;
import arkin.LED;
import arkin.LT;
import arkin.LTE;
import arkin.LightSensor;
import arkin.NOT;
import arkin.OR;
import arkin.Pen;
import arkin.Robot;
import arkin.Sensor;
import arkin.State;
import arkin.TouchSensor;
import arkin.Transition;
import arkin.XOR;

public class Visitor implements IVisitor {
	
	public static String VALUES = "values";
	public static String TIME_STEP = "TIME_STEP";
	public static String COURANTS = "courants";
	public static String TABULATION = "\t";

	private ArkinTemplate b;


	public Visitor(ArkinTemplate a) {
		b = a;
	}
	
	
	public String getEntryByFSM(FSM fsm) {
		String s = "pas d'entry";
		for (State state : fsm.getStates())
			if (state instanceof EntryState)
				s = state.getName();
		return s;
	}


	@Override
	public void visit(ActionDifferentialWheel a) {
		b.methods_states.append("this.setSpeed(" + a.getLeftValue() + ", "
				+ a.getRightValue() + ");\n");// append("allumerLED("+a.getActuators().getName()+");");
	}

	@Override
	public void visit(ActionLED a) {
		if (a.isOn()) {
			b.methods_states.append(a.getActuator().getName() + ".set(1);\n");// append("allumerLED("+a.getActuators().getName()+");");
		} else {
			b.methods_states.append(a.getActuator().getName() + ".set(0);\n");// .append("eteindreLED("+a.getActuators().getName()+");");
		}
	}

	@Override
	public void visit(AND a) {
		b.methods_states.append("(");
		if (a.getOp1() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp1()).getName()
					+ ".getValue();");
		} else if (a.getOp1() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp1()).getName() + ");");
		} else {
			a.getOp1().accept(this);
		}
		b.methods_states.append("&&");
		if (a.getOp2() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp2()).getName()
					+ ".getValue();");
		} else if (a.getOp2() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp2()).getName() + ");");
		} else {
			a.getOp2().accept(this);
		}
		b.methods_states.append(")");
	}

	@Override
	public void visit(Arbiter a) {
		b.run.append("doArbiter();\n");

		b.methods.append("public void doArbiter(){\n");
		for (Behavior behav : a.getBehaviors()) {
			behav.accept(this);
		}
		b.methods.append("}\n");
	}

	@Override
	public void visit(Behavior a) {
		String entryName = getEntryByFSM(a.getFsm());
		b.constructor.append(COURANTS + ".put(\"" + a.getName() + "\", \""
				+ entryName + "\");\n");

		// Creation de la méthode de ce comportement
		b.methods.append("if(" + a.getName() + "()) return;\n");

		// ajout du comportement dans la boucle principale
		b.methods_states.append("public boolean " + a.getName() + "(){\n");
		b.methods_states.append("boolean inibit = false;\n");
		b.methods_states.append("double value = 0.0;\n");
		b.methods_states.append("String courant = " + COURANTS + ".get(\""
				+ a.getName() + "\");\n");
		b.methods_states.append("String self = \"" + a.getName() + "\";\n");
		a.getFsm().accept(this);
		b.methods_states.append(COURANTS + ".put(\"" + a.getName() + "\", "
				+ "courant);\n");
		b.methods_states.append(VALUES + ".put(\"" + a.getName() + "\", "
				+ "value);\n");
		b.methods_states.append("return inibit;\n\n");
		b.methods_states.append("}\n\n");
	}

	@Override
	public void visit(Boolean a) {
		b.methods_states.append(a.isBool());
	}

	@Override
	public void visit(Constante a) {
		b.methods_states.append(a.getValue());
	}

	@Override
	public void visit(DistanceSensor a) {
		// declaration en attribut de classe
		b.attributes.append("private DistanceSensor " + a.getName() + ";\n");

		// initialisation dans le constructeur
		b.constructor.append(a.getName() + " = this.getDistanceSensor(\""
				+ a.getName() + "\");\n");
		b.constructor.append(a.getName() + ".enable(" + TIME_STEP + ");\n");

		// mise à jour des valeurs au début de la boucle run
		b.run.append(VALUES + ".put(\"" + a.getName() + "\", " + a.getName()
				+ ".getValue());\n");
	}

	@Override
	public void visit(EQUAL a) {
		b.methods_states.append("(");
		if (a.getOp1() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp1()).getName()
					+ ".getValue();");
		} else if (a.getOp1() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp1()).getName() + ");");
		} else {
			a.getOp1().accept(this);
		}
		b.methods_states.append("==");
		if (a.getOp2() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp2()).getName()
					+ ".getValue();");
		} else if (a.getOp2() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp2()).getName() + ");");
		} else {
			a.getOp2().accept(this);
		}
		b.methods_states.append(")");
	}

	@Override
	public void visit(FSM a) {
		int i = 0;
		for (State state : a.getStates()) {
			if (i++ != 0) {
				b.methods_states.append("else ");
			}
			b.methods_states.append("if(courant.equals(\"" + state.getName()
					+ "\")){\n");
			state.accept(this);
			b.methods_states.append("}\n");
		}

	}

	@Override
	public void visit(GT a) {
		b.methods_states.append("(");
		if (a.getOp1() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp1()).getName()
					+ ".getValue();");
		} else if (a.getOp1() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp1()).getName() + ");");
		} else {
			a.getOp1().accept(this);
		}
		b.methods_states.append(">");
		if (a.getOp2() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp2()).getName()
					+ ".getValue();");
		} else if (a.getOp2() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp2()).getName() + ");");
		} else {
			a.getOp2().accept(this);
		}
		b.methods_states.append(")");
	}

	@Override
	public void visit(GTE a) {
		b.methods_states.append("(");
		if (a.getOp1() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp1()).getName()
					+ ".getValue();");
		} else if (a.getOp1() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp1()).getName() + ");");
		} else {
			a.getOp1().accept(this);
		}
		b.methods_states.append(">=");
		if (a.getOp2() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp2()).getName()
					+ ".getValue();");
		} else if (a.getOp2() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp2()).getName() + ");");
		} else {
			a.getOp2().accept(this);
		}
		b.methods_states.append(")");

	}

	@Override
	public void visit(LED a) {
		b.attributes.append("private LED " + a.getName() + ";\n");

		b.constructor.append(a.getName() + " = this.getLED(\"" + a.getName()
				+ "\");\n");
	}

	@Override
	public void visit(LightSensor a) {
		// declaration en attribut de classe
		b.attributes.append("private LightSensor " + a.getName() + ";\n");

		// initialisation dans le constructeur
		b.constructor.append(a.getName() + " = this.getLightSensor(\""
				+ a.getName() + "\");\n");
		b.constructor.append(a.getName() + ".enable(" + TIME_STEP + ");\n");

		// mise à jour des valeurs au début de la boucle run
		b.run.append(VALUES + ".put(\"" + a.getName() + "\", " + a.getName()
				+ ".getValue());\n");
	}

	@Override
	public void visit(LT a) {
		b.methods_states.append("(");
		if (a.getOp1() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp1()).getName()
					+ ".getValue();");
		} else if (a.getOp1() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp1()).getName() + ");");
		} else {
			a.getOp1().accept(this);
		}
		b.methods_states.append("<");
		if (a.getOp2() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp2()).getName()
					+ ".getValue();");
		} else if (a.getOp2() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp2()).getName() + ");");
		} else {
			a.getOp2().accept(this);
		}
		b.methods_states.append(")");
	}

	@Override
	public void visit(LTE a) {
		b.methods_states.append("(");
		if (a.getOp1() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp1()).getName()
					+ ".getValue();");
		} else if (a.getOp1() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp1()).getName() + ");");
		} else {
			a.getOp1().accept(this);
		}
		b.methods_states.append("<=");
		if (a.getOp2() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp2()).getName()
					+ ".getValue();");
		} else if (a.getOp2() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp2()).getName() + ");");
		} else {
			a.getOp2().accept(this);
		}
		b.methods_states.append(")");
	}

	@Override
	public void visit(NOT a) {
		b.methods_states.append("(!");
		if (a.getOp() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp()).getName()
					+ ".getValue();");
		} else if (a.getOp() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp()).getName() + ");");
		} else {
			a.getOp().accept(this);
		}
		b.methods_states.append(")");
	}

	@Override
	public void visit(OR a) {
		b.methods_states.append("(");
		if (a.getOp1() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp1()).getName()
					+ ".getValue();");
		} else if (a.getOp1() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp1()).getName() + ");");
		} else {
			a.getOp1().accept(this);
		}
		b.methods_states.append("||");
		if (a.getOp2() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp2()).getName()
					+ ".getValue();");
		} else if (a.getOp2() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp2()).getName() + ");");
		} else {
			a.getOp2().accept(this);
		}
		b.methods_states.append(")");
	}

	@Override
	public void visit(Robot a) {
		// IMPORT
		b.imports.append("import java.util.HashMap;\n");
		b.imports.append("import com.cyberbotics.webots.controller.*;\n");

		// CLASS
		boolean bo = false;
		for(Actuator act: a.getActuators()){
			if(act instanceof DifferentialWheel){
				bo=true;
			} 
		}
		b.className = a.getName();
		b.extendz = (bo)?"DifferentialWheels":"Robot";

		// CONSTRUCTOR
		b.constructor.append("super();\n");

		// METHODS
		b.methods.append("public static void main(String[] args) {\n"
				+ a.getName() + " controller = new " + a.getName() + "();\n"
				+ "controller.run();\n" + "}\n\n");

		// ATTRIBUTES
		b.attributes.append("private final int " + TIME_STEP + " = 32;\n");
		// b.attributes.append("private final double maxSpeed = 100.0;\n");
		b.attributes.append("private HashMap<String, String> " + COURANTS
				+ " = new HashMap <String, String>();\n");
		b.attributes.append("private HashMap<String, Double> " + VALUES
				+ " = new HashMap<String, Double>();\n");

		for (Actuator act : a.getActuators()) {
			act.accept(this);
		}

		for (Sensor s : a.getSensors()) {
			s.accept(this);
		}

		a.getArbiter().accept(this);
	}

	@Override
	public void visit(State a) {
		int i = 0;
		for (Transition t : a.getTransitions()) {
			if (i++ != 0) {
				b.methods_states.append("else ");
			}
			t.accept(this);
		}
		b.methods_states.append("else {\n ");
		for (Action ac : a.getActions()) {
			ac.accept(this);
		}
		b.methods_states.append("}\n");
	}

	@Override
	public void visit(Transition a) {
		b.methods_states.append("if(");
		a.getCondition().accept(this);
		b.methods_states.append("){\n");

		b.methods_states.append("courant = \"" + a.getTarget().getName()
				+ "\";\n");
		b.methods_states.append("inibit = " + a.getTarget().isIsInibiteur()
				+ ";\n");
		b.methods_states.append("value = " + a.getTarget().getValue() + ";\n");

		for (Action act : a.getTarget().getActions()) {
			act.accept(this);
		}

		b.methods_states.append("}\n");
	}

	@Override
	public void visit(XOR a) {
		b.methods_states.append("(");
		if (a.getOp1() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp1()).getName()
					+ ".getValue();");
		} else if (a.getOp1() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp1()).getName() + ");");
		} else {
			a.getOp1().accept(this);
		}
		b.methods_states.append("^");
		if (a.getOp2() instanceof Sensor) {
			b.methods_states.append(((Sensor) a.getOp2()).getName()
					+ ".getValue();");
		} else if (a.getOp2() instanceof Behavior) {
			b.methods_states.append(VALUES + ".get("
					+ ((Behavior) a.getOp2()).getName() + ");");
		} else {
			a.getOp2().accept(this);
		}
		b.methods_states.append(")");
	}

	@Override
	public void visit(ActionPen a) {
		int color = 0;
		switch (a.getColor()) {
		case BLACK:
			color = 0x000000;
			break;
		case RED:
			color = 0xFF0000;
			break;
		case GREEN:
			color = 0x00FF00;
			break;
		case BLUE:
			color = 0x0000FF;
			break;
		}
		b.methods_states.append(a.getActuator().getName() + ".setInkColor("
				+ color + ");\n");
		b.methods_states.append(a.getActuator().getName() + ".write("
				+ a.isDown() + ");\n");
	}

	@Override
	public void visit(Pen a) {
		b.attributes.append("private Pen " + a.getName() + ";\n");

		b.constructor.append(a.getName() + " = this.getPen(\"" + a.getName()
				+ "\");\n");

	}

	@Override
	public void visit(TouchSensor a) {
		// declaration en attribut de classe
		b.attributes.append("private TouchSensor " + a.getName() + ";\n");

		// initialisation dans le constructeur
		b.constructor.append(a.getName() + " = this.getTouchSensor(\""
				+ a.getName() + "\");\n");
		b.constructor.append(a.getName() + ".enable(" + TIME_STEP + ");\n");

		// mise à jour des valeurs au début de la boucle run
		b.run.append(VALUES + ".put(\"" + a.getName() + "\", " + a.getName()
				+ ".getValue());\n");
	}

	/*************************************************
	 *************************************************/

	@Override
	public EClass eClass() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Resource eResource() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public EObject eContainer() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public EStructuralFeature eContainingFeature() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public EReference eContainmentFeature() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public EList<EObject> eContents() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public TreeIterator<EObject> eAllContents() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean eIsProxy() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public EList<EObject> eCrossReferences() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object eGet(EStructuralFeature feature) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object eGet(EStructuralFeature feature, boolean resolve) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void eSet(EStructuralFeature feature, Object newValue) {
		// TODO Auto-generated method stub

	}

	@Override
	public boolean eIsSet(EStructuralFeature feature) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void eUnset(EStructuralFeature feature) {
		// TODO Auto-generated method stub

	}

	@Override
	public Object eInvoke(EOperation operation, EList<?> arguments)
			throws InvocationTargetException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public EList<Adapter> eAdapters() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean eDeliver() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public void eSetDeliver(boolean deliver) {
		// TODO Auto-generated method stub

	}

	@Override
	public void eNotify(Notification notification) {
		// TODO Auto-generated method stub

	}

}
